{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Common Set Operations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's look at some of the more basic and common operations with sets:\n",
    "* size\n",
    "* membership testing\n",
    "* adding elements\n",
    "* removing elements"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Size"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The size of a set (it's cardinality), is given by the `len()` function - the same one we use for sequences, iterables, dictionaries, etc."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s = {1, 2, 3}\n",
    "len(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Membership Testing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is also very easy:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = {1, 2, 3}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "1 in s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "10 in s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "1 not in s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "10 not in s"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But let's go a little further and consider how membership testing works with sets. As I mentioned in earlier lectures, sets are hash tables, and membership testing is **extremely** efficient for sets, since it's simply a hash table lookup - as opposed to scanning a list for example, until we find the requested element (or not).\n",
    "\n",
    "Let's do some quick timings to verify this, as well as compare lookup speeds for sets and dictionaries as well (which are also, after all, hash tables)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "from timeit import timeit"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "n = 100_000\n",
    "s = {i for i in range(n)}\n",
    "l = [i for i in range(n)]\n",
    "d = {i:None for i in range(n)}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's time how long it takes to find if `9` is in the object - which would be the tenth element only of the list and the dictionary (keys), and who knows for the set:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "list: 0.09865150199038908\n",
      "set: 0.025414875999558717\n",
      "dict: 0.029280081973411143\n"
     ]
    }
   ],
   "source": [
    "number = 1_000_000\n",
    "search = 9\n",
    "t_list = timeit(f'{search} in l', globals=globals(), number=number)\n",
    "t_set = timeit(f'{search} in s', globals=globals(), number=number)\n",
    "t_dict = timeit(f'{search} in d', globals=globals(), number=number)\n",
    "print('list:', t_list)\n",
    "print('set:', t_set)\n",
    "print('dict:', t_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The story changes even more if we test for example the last element of the list.\n",
    "I'm definitely not to run the tests `1_000_000` times - not unless we want to make this video reaaaaaaly long!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "list: 2.287420156993903\n",
      "set: 9.811401832848787e-05\n",
      "dict: 0.00010706903412938118\n"
     ]
    }
   ],
   "source": [
    "number = 3_000\n",
    "search = 99_999\n",
    "t_list = timeit(f'{search} in l', globals=globals(), number=number)\n",
    "t_set = timeit(f'{search} in s', globals=globals(), number=number)\n",
    "t_dict = timeit(f'{search} in d', globals=globals(), number=number)\n",
    "print('list:', t_list)\n",
    "print('set:', t_set)\n",
    "print('dict:', t_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The situation for `not in` is the same:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "list: 2.031598252011463\n",
      "set: 7.687101606279612e-05\n",
      "dict: 7.940799696370959e-05\n"
     ]
    }
   ],
   "source": [
    "number = 3_000\n",
    "search = -1\n",
    "t_list = timeit(f'{search} not in l', globals=globals(), number=number)\n",
    "t_set = timeit(f'{search} not in s', globals=globals(), number=number)\n",
    "t_dict = timeit(f'{search} not in d', globals=globals(), number=number)\n",
    "print('list:', t_list)\n",
    "print('set:', t_set)\n",
    "print('dict:', t_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But this efficiency does come at the cost of memory:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "5242952\n",
      "4194504\n",
      "824440\n"
     ]
    }
   ],
   "source": [
    "print(d.__sizeof__())\n",
    "print(s.__sizeof__())\n",
    "print(l.__sizeof__())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Even for empty objects:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = set()\n",
    "d = dict()\n",
    "l = list()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "216\n",
      "200\n",
      "40\n"
     ]
    }
   ],
   "source": [
    "print(d.__sizeof__())\n",
    "print(s.__sizeof__())\n",
    "print(l.__sizeof__())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And adding just one element to each object:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "s.add(10)\n",
    "d[10] =None\n",
    "l.append(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "216\n",
      "200\n",
      "72\n"
     ]
    }
   ],
   "source": [
    "print(d.__sizeof__())\n",
    "print(s.__sizeof__())\n",
    "print(l.__sizeof__())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you're wondering why the dictionary and set size did not increase, remember when we covered hash tables - there is some overallocation that takes place so we don't incure the cost of resizing every time we had an element. In fact, lists do the same as well - they over-allocate to reduce the resizing cost. I'll come back to that in a minute."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Adding Elements"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we have an existing set, we can always add elements to it. Of course *where* it gets \"inserted\" is unknown. So Python does not call it `append` or `insert` which would connotate ordering of some kind - instead it just calls it `add`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = {30, 20, 10}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "s.add(15)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{10, 15, 20, 30}"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Don't be fooled by the apparent ordering of the elements here. This is the same as with dictionaries - Jupyter tries to represent things nicely for us, but underneath the scenes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{10, 20, 30, 15}\n"
     ]
    }
   ],
   "source": [
    "print(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{10, 15, 20, 30, -1}\n"
     ]
    }
   ],
   "source": [
    "s.add(-1)\n",
    "print(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And the order just changed again! :-)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What's interesting about the `add()` method, is that if we try to add an element that already exists, Python will simply ignore it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{-1, 10, 15, 20, 30}"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "s.add(15)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{-1, 10, 15, 20, 30}"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that we know how to add an element to a set, let's go back and see how  the set, dictionary and list resize as we add more elements to them.\n",
    "We should expect the list to be more efficient from a memory standpoint:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "# dict set list\n",
      "0 216 200 40\n",
      "1 216 200 72\n",
      "2 216 200 72\n",
      "3 216 200 72\n",
      "4 216 200 72\n",
      "5 216 712 104\n",
      "6 344 712 104\n",
      "7 344 712 104\n",
      "8 344 712 104\n",
      "9 344 712 168\n",
      "10 344 712 168\n",
      "11 624 712 168\n",
      "12 624 712 168\n",
      "13 624 712 168\n",
      "14 624 712 168\n",
      "15 624 712 168\n",
      "16 624 712 168\n",
      "17 624 712 240\n",
      "18 624 712 240\n",
      "19 624 712 240\n",
      "20 624 712 240\n",
      "21 624 2248 240\n",
      "22 1160 2248 240\n",
      "23 1160 2248 240\n",
      "24 1160 2248 240\n",
      "25 1160 2248 240\n",
      "26 1160 2248 320\n",
      "27 1160 2248 320\n",
      "28 1160 2248 320\n",
      "29 1160 2248 320\n",
      "30 1160 2248 320\n",
      "31 1160 2248 320\n",
      "32 1160 2248 320\n",
      "33 1160 2248 320\n",
      "34 1160 2248 320\n",
      "35 1160 2248 320\n",
      "36 1160 2248 408\n",
      "37 1160 2248 408\n",
      "38 1160 2248 408\n",
      "39 1160 2248 408\n",
      "40 1160 2248 408\n",
      "41 1160 2248 408\n",
      "42 1160 2248 408\n",
      "43 2256 2248 408\n",
      "44 2256 2248 408\n",
      "45 2256 2248 408\n",
      "46 2256 2248 408\n",
      "47 2256 2248 504\n",
      "48 2256 2248 504\n",
      "49 2256 2248 504\n"
     ]
    }
   ],
   "source": [
    "l = list()\n",
    "s = set()\n",
    "d = dict()\n",
    "\n",
    "print('#', 'dict', 'set', 'list')\n",
    "for i in range(50):\n",
    "    print(i, d.__sizeof__(), s.__sizeof__(), l.__sizeof__())\n",
    "    l.append(i)\n",
    "    s.add(i)\n",
    "    d[i] = None"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, the memory costs for a set or a dict are definitely higher than for a list. You can also see from this how it looks like CPython implements different resizing strategies for sets, dicts and lists.\n",
    "The strategy by the way has nothing to do with the size of the elements we put in those objects:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "# dict set list\n",
      "0 216 200 40\n",
      "1 216 200 72\n",
      "2 216 200 72\n",
      "3 216 200 72\n",
      "4 216 200 72\n",
      "5 216 712 104\n",
      "6 344 712 104\n",
      "7 344 712 104\n",
      "8 344 712 104\n",
      "9 344 712 168\n",
      "10 344 712 168\n",
      "11 624 712 168\n",
      "12 624 712 168\n",
      "13 624 712 168\n",
      "14 624 712 168\n",
      "15 624 712 168\n",
      "16 624 712 168\n",
      "17 624 712 240\n",
      "18 624 712 240\n",
      "19 624 712 240\n",
      "20 624 712 240\n",
      "21 624 2248 240\n",
      "22 1160 2248 240\n",
      "23 1160 2248 240\n",
      "24 1160 2248 240\n",
      "25 1160 2248 240\n",
      "26 1160 2248 320\n",
      "27 1160 2248 320\n",
      "28 1160 2248 320\n",
      "29 1160 2248 320\n",
      "30 1160 2248 320\n",
      "31 1160 2248 320\n",
      "32 1160 2248 320\n",
      "33 1160 2248 320\n",
      "34 1160 2248 320\n",
      "35 1160 2248 320\n",
      "36 1160 2248 408\n",
      "37 1160 2248 408\n",
      "38 1160 2248 408\n",
      "39 1160 2248 408\n",
      "40 1160 2248 408\n",
      "41 1160 2248 408\n",
      "42 1160 2248 408\n",
      "43 2256 2248 408\n",
      "44 2256 2248 408\n",
      "45 2256 2248 408\n",
      "46 2256 2248 408\n",
      "47 2256 2248 504\n",
      "48 2256 2248 504\n",
      "49 2256 2248 504\n"
     ]
    }
   ],
   "source": [
    "l = list()\n",
    "s = set()\n",
    "d = dict()\n",
    "\n",
    "print('#', 'dict', 'set', 'list')\n",
    "for i in range(50):\n",
    "    print(i, d.__sizeof__(), s.__sizeof__(), l.__sizeof__())\n",
    "    l.append(i**1000)\n",
    "    s.add(i*1000)\n",
    "    d[i*1000] = None"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see the memory cost of the objects themselves did not change, nor did the sizing strategy (remember that all those objects contain pointers to the data, not the data itself - and a pointer to an object, no matter the size of that object, is the same).\n",
    "So be careful using `__sizeof__` - it's often only part of the story."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Removing Elements"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's see how we can remove elements from a set.\n",
    "\n",
    "Just as with dictionaries, we may be trying to remove an item that does not exist in the set. Depending on whether we want to silently ignore deletion of non-existent elements we can use one of two techniques:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = {1, 2, 3}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "s.remove(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{2, 3}"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "ename": "KeyError",
     "evalue": "10",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mKeyError\u001b[0m                                  Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-30-23b5f1bb77c8>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0ms\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mremove\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mKeyError\u001b[0m: 10"
     ]
    }
   ],
   "source": [
    "s.remove(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, we get an exception.\n",
    "\n",
    "If we don't want the exception we can do it this way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "s.discard(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{2, 3}"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also remove (and return) an **arbitrary** element from the set:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = set('python')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'h', 'n', 'o', 'p', 't', 'y'}"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'h'"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s.pop()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that we **do not know** ahead of time what element will get popped.\n",
    "\n",
    "Also, popping an empty set will result in a `KeyError` exception:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "ename": "KeyError",
     "evalue": "'pop from an empty set'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mKeyError\u001b[0m                                  Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-36-261aed8e48a7>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0ms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0ms\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mKeyError\u001b[0m: 'pop from an empty set'"
     ]
    }
   ],
   "source": [
    "s = set()\n",
    "s.pop()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Something like that might be handy to handle all the elements of a set one at a time without caring for the order in which elements are removed from the set - not that you can, anyway - sets are not ordered!\n",
    "But this way you can get at the elements of a set without knowing the content of the set (since you need to know the element you are removing with `remove` and `discard`.)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, you can empty out a set by calling the `clear` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "set()"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s = {1, 2, 3}\n",
    "s.clear()\n",
    "s"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
